import time
from Bagging_ForP import Bagging
import numpy as np


print('---------------------------------')
def load_iris(file_path):
    data = []
    label = []
    class_mapping = {}
    index = 0
    with open(file_path, 'r') as file:
        # 将全部内容读到一个列表中，列表每个元素即：一行代表一个样本
        lines = file.readlines()
        for line in lines:
            # 去除每行中的空格和换行符，使用','对数据进行分割
            # parts是一个列表
            # 一行有5个元素，特征占4个，类别名称1个
            parts = line.strip().split(',')

            # 取列表中的前4个元素作为特征数据            
            valid_features = [num for num in parts[:4] if num.strip()]
            # 列表元素的转换成float，然后将列表变成数组array
            features = np.array([float(num) for num in valid_features])
            
            # 取列表的最后一个元素为标签名
            class_name = parts[4].strip()
            # 为了方便分类，需要用 数字 来代表 标签名
            if class_name not in class_mapping:
                # 使用字典的数据结构，构建 标签名 的 数字 对应表
                '''
                {'Iris-setosa': 0, 'Iris-versicolor': 1, 'Iris-virginica': 2}
                '''
                class_mapping[class_name] = index
                index += 1
            # 通过数字对应表，标签名 的 对应数字
            class_index = class_mapping[class_name]
            # 将处理好的特征和标签，放到对应的存储列表中
            data.append(features)
            label.append(class_index)
    # 返回特征data 标签label，转换数据类型为数组
    return np.array(data), np.array(label)

# 导入数据集
file_path = 'iris.txt'
X, y = load_iris(file_path)
print('原数据集样本数:',X.shape[0])

# 获取数据集的总样本数量
n_samples = X.shape[0]
# 设置测试集的样本数量（设置为三分之一）
n_test = int(n_samples * 0.2)
# 生成随机索引，随机选择测试集样本
np.random.seed(42) 
# 从150个索引中不放回抽取三分之一作为测试集的索引
rdm_test_idx = np.random.choice(n_samples, n_test, replace=True)
# 将150个索引中剩下的索引作为训练集的索引
rdm_train_idx = np.setdiff1d(np.arange(n_samples), rdm_test_idx)

# 根据索引划分训练集和测试集
train_X = X[rdm_train_idx] # 训练集特征
train_y = y[rdm_train_idx] # 训练集标签

test_X = X[rdm_test_idx] # 测试集特征
test_y = y[rdm_test_idx] # 训练集标签

print('训练集样本数:',train_X.shape[0])
print('测试集样本数:',test_X.shape[0])

print('数据集处理完成')
print('---------------------------------')



'''
决策树数量：1-10
最大深度：1-10
'''
with open('Iris_Bagging.txt','w+',encoding='utf-8') as f:
    for tree in range(1,11):
        for depth in range(1,11):
            # 创建Bagging分类器对象
            # Bagging和RandomForest的区别是，RandomForest在自举的基础上，加上了随机特征抽取的操作
            # max_features=None，使用全部特征进行训练，不随机抽取部分特征进行训练
            bg = Bagging(num_trees=tree, max_depth=depth, max_features=None, bootstrap=True, random_state=55)

            f.write('---------------------------------\n')
            f.write(f'Tree num = {tree}, max_depth = {depth}\n')

            print('Bagging分类器构建成功')
            print('---------------------------------')
            # 在训练集上训练模型
            # 记录训练开始时间
            print('开始训练')
            start_train_time = time.time()
            bg.fit(train_X, train_y)
            # 记录训练结束时间并计算训练用时
            train_time = time.time() - start_train_time
            print('训练完成')
            print("训练时间: {:.8f} s".format(train_time))

            f.write("训练时间: {:.8f} s\n".format(train_time))
            print('---------------------------------')
            # 在测试集上进行预测
            # 记录预测开始时间
            print('开始预测')
            start_predict_time = time.time()
            y_pred = bg.predict(test_X)
            # 记录预测结束时间并计算预测用时
            predict_time = time.time() - start_predict_time
            print('预测完成')
            print("预测时间: {:.12f} s".format(predict_time))

            f.write("预测时间: {:.12f} s\n".format(predict_time))
            print('---------------------------------')


            # 计算预测正确的样本数，通过比较预测结果y_pred和真实标签test_y中相等的元素个数来统计
            correct_count = np.sum(test_y == y_pred)
            # 计算准确率，将预测正确的样本数除以测试集样本总数（test_y的长度）
            accuracy = correct_count / len(test_y)
            print("准确率: {:.2f}%".format(accuracy * 100))
            f.write("准确率: {:.2f}%\n".format(accuracy * 100))

            # 获取类别数量，假设类别标签是从0开始连续的整数，通过取最大标签值加1来获取类别数量
            num_classes = int(np.max(test_y)) + 1
            # 初始化混淆矩阵，形状为（类别数量，类别数量），初始值全为0
            confusion_matrix = np.zeros((num_classes, num_classes))
            # 遍历预测结果和真实标签，填充混淆矩阵
            for true_label, pred_label in zip(test_y, y_pred):
                confusion_matrix[true_label][pred_label] += 1

            # 计算精确率
            precision_sum = 0
            for i in range(num_classes):
                true_positives = confusion_matrix[i][i]
                predicted_positives = np.sum(confusion_matrix[:, i])
                if predicted_positives > 0:
                    precision_sum += true_positives / predicted_positives
            precision = precision_sum / num_classes
            print("精确率: {:.2f}%".format(precision * 100))
            f.write("精确率: {:.2f}%\n".format(precision * 100))


            # 计算召回率
            recall_sum = 0
            for i in range(num_classes):
                true_positives = confusion_matrix[i][i]
                actual_positives = np.sum(confusion_matrix[i, :])
                if actual_positives > 0:
                    recall_sum += true_positives / actual_positives
            recall = recall_sum / num_classes
            print("召回率: {:.2f}%".format(recall * 100))
            f.write("召回率: {:.2f}%\n".format(recall * 100))

            # 计算F1分数
            if precision + recall > 0:
                f1_score = 2 * (precision * recall) / (precision + recall)
                print("F1分数: {:.2f}%".format(f1_score * 100))
                f.write("F1分数: {:.2f}%\n".format(f1_score * 100))
            else:
                print("由于精确率和召回率之和为0，无法计算F1分数")
                f.write("由于精确率和召回率之和为0，无法计算F1分数\n")

            print('---------------------------------\n')
            f.write('---------------------------------\n\n')

